TimeSpanParser Class

Extended duration parsing with calendar-based units and human-readable formats

reference
time
duration
parsing
calendar
Author

Diginsight Components

Published

March 17, 2026

The TimeSpanParser provides extended duration parsing with support for calendar-based units like years (‘Y’) and months (‘M’) that cannot be precisely represented as fixed TimeSpans.

In particular, it enables human-readable duration expressions (e.g., “6M”, “1.5Y”, “2W3D”) while maintaining backward compatibility with standard TimeSpan.Parse formats.

TimeSpanParser allows parsing those expressions with Parse() GetExpressionOccurrence() that can be used to calendar independent intervals (based on standard 365 days year), or calendar-accurate date calculations (based on a reference date).

Table of Contents

📋 Overview

The TimeSpanParser extends standard .NET TimeSpan functionality by supporting calendar-based duration units (years and months) that have variable lengths. It provides a simple, human-readable syntax for expressing durations while offering both approximate TimeSpan conversions and calendar-accurate date arithmetic.

Key Features

  • Calendar-Based Units: Support for years (Y) and months (M) in addition to standard TimeSpan units
  • Human-Readable Format: Intuitive expression syntax (e.g., “6M”, “1.5Y”, “2W3D”)
  • Fractional Values: Decimal support for all time units (e.g., “1.5Y”, “2.5M”)
  • Multiple Parsing Modes: Generic (approximate) and CalendarAccurate parsing strategies
  • Dual Calculation Modes: Both approximate TimeSpan conversion and calendar-accurate date calculations
  • Backward Compatible: Falls back to standard TimeSpan.Parse for compatibility
  • Case Insensitive: Accepts both uppercase and lowercase unit identifiers
  • Safe Parsing: TryParse method for error-free parsing scenarios
  • Predefined Examples: Built-in constants for common duration patterns

Supported Units

The parser supports the following time units in descending order:

Unit Identifier Case Sensitivity Example Description
Years Y, y Insensitive 1Y”, “1.5Y Calendar years (365.25 days average)
Months M Sensitive (uppercase only) 6M”, “2.5M Calendar months (30.44 days average)
Weeks W, w Insensitive 2W”, “1.5W Weeks (7 days)
Days D, d Insensitive 30D”, “1.5D Days (24 hours)
Hours H, h Insensitive 12H”, “0.5H Hours (60 minutes)
Minutes m Sensitive (lowercase only) 30m”, “1.5m Minutes (60 seconds)
Seconds S, s Insensitive 45S”, “1.5S Seconds

Example expressions: “6M”, “1.5Y”, “2W3D”, “1Y6M2W1D12H30m45S”

Important: The identifiers M (months, uppercase) and m (minutes, lowercase) are case-sensitive to distinguish between these two units.

Parsing Types

The parser supports two parsing strategies via the ParsingType enum:

  • ParsingType.Generic: Uses approximate conversions (1 year = 365.25 days, 1 month = 30.44 days)
  • ParsingType.CalendarAccurate: Uses calendar-accurate calculations with actual month/year handling relative to a reference date

🔍 Additional Details

Approximate vs Calendar-Accurate Calculations

The TimeSpanParser provides two different calculation approaches depending on your needs:

Approximate TimeSpan Conversion (obtained with Parse(string) or ParsingType.Generic argument): - Converts all units to a single TimeSpan using average values - 1 year = 365.25 days (accounts for leap years) - 1 month = 30.44 days (365.25 / 12) - Best for: Duration measurements, timeouts, intervals, approximate comparisons

Calendar-Accurate Calculation (obtained with ParsingType.CalendarAccurate arguments): - Uses DateTimeOffset.AddYears and AddMonths for precise calendar arithmetic - Properly handles varying month lengths (28-31 days) - Correctly accounts for leap years - Best for: Calculating past dates like retention periods, lookback windows, historical data queries

// Approximate: Always returns same TimeSpan
var duration = TimeSpanParser.Parse("1M");  // ~30.44 days

// Calendar-accurate with ParsingType
var now = DateTimeOffset.UtcNow;
var exactDuration = TimeSpanParser.Parse("1M", ParsingType.CalendarAccurate, now, -1);
// Returns actual TimeSpan between now and 1 month ago

// Calendar-accurate: Respects actual month length
var jan31 = new DateTimeOffset(2024, 1, 31, 0, 0, 0, TimeSpan.Zero);
var result = TimeSpanParser.GetExpressionOccurrence(jan31, "1M", -1);
// Result: December 31, 2023 (1 month back from Jan 31, 2024)

var mar31 = new DateTimeOffset(2024, 3, 31, 0, 0, 0, TimeSpan.Zero);
var result2 = TimeSpanParser.GetExpressionOccurrence(mar31, "1M", -1);
// Result: February 29, 2024 (1 month back from Mar 31, 2024 - handles leap year)

Fractional Values

All time units support fractional (decimal) values:

// Fractional years converted to months
var oneAndHalfYears = TimeSpanParser.Parse("1.5Y");
// Equivalent to: 1 year + 6 months

// Fractional months converted to days
var twoAndHalfMonths = TimeSpanParser.Parse("2.5M");
// Equivalent to: 2 months + ~15 days

// Fractional smaller units
var oneAndHalfWeeks = TimeSpanParser.Parse("1.5W");
// Equivalent to: 10.5 days

Conversion Rules: - Fractional years → converted to whole months (1.5Y → 1Y + 6M) - Fractional months → converted to days using 30.44 days/month - Fractional weeks, days, hours, minutes, seconds → converted directly

Case Sensitivity

The parser is case-insensitive for most unit identifiers, with two important exceptions:

// Case-insensitive units
TimeSpanParser.Parse("1Y");   // 1 year
TimeSpanParser.Parse("1y");   // 1 year (same)
TimeSpanParser.Parse("2W");   // 2 weeks
TimeSpanParser.Parse("2w");   // 2 weeks (same)

// Case-SENSITIVE units - Must use exact case:
// M (uppercase) = Months ONLY
TimeSpanParser.Parse("6M");   // 6 months ✓
TimeSpanParser.Parse("6m");   // 6 MINUTES (not months!) ✓

// m (lowercase) = Minutes ONLY
TimeSpanParser.Parse("30m");  // 30 minutes ✓
TimeSpanParser.Parse("30M");  // Would fail - M without digits before it is invalid

Important: - M (uppercase) = Months (case-sensitive, uppercase only) - m (lowercase) = Minutes (case-sensitive, lowercase only) - All other units (Y, W, D, H, S) are case-insensitive

Best Practice: For clarity and consistency, use the exact case shown in the documentation: - Years: Y or y - Months: M (uppercase only) - Weeks: W or w - Days: D or d - Hours: H or h - Minutes: m (lowercase only) - Seconds: S or s

Backward Compatibility

The parser maintains full compatibility with standard TimeSpan.Parse:

// Standard TimeSpan formats work
var ts1 = TimeSpanParser.Parse("01:30:00");        // 1 hour 30 minutes
var ts2 = TimeSpanParser.Parse("1.12:30:00");      // 1 day, 12 hours, 30 minutes
var ts3 = TimeSpanParser.Parse("00:00:45");        // 45 seconds

// Extended formats
var ts4 = TimeSpanParser.Parse("6M");              // ~6 months
var ts5 = TimeSpanParser.Parse("1Y6M");            // ~1.5 years

The parser attempts standard TimeSpan.Parse first, then falls back to extended format parsing if that fails.

Unit Ordering

Units must appear in descending order (largest to smallest):

// ✓ Correct ordering
TimeSpanParser.Parse("1Y6M2W3D12H30m45S");

// ✗ Incorrect ordering (will fail to parse)
TimeSpanParser.Parse("6M1Y");     // Months before years
TimeSpanParser.Parse("3D2W");     // Days before weeks
TimeSpanParser.Parse("45S30m");   // Seconds before minutes

// ✓ You can skip units
TimeSpanParser.Parse("1Y3D");     // Year and days (skipping months and weeks)
TimeSpanParser.Parse("2W12H");    // Weeks and hours (skipping days)

⚙️ Configuration

Default Period

When no expression is provided or the expression is null/whitespace, the parser uses a default period:

public const string DefaultPeriod = "1M";  // 1 month

// These all return 1 month duration
var default1 = TimeSpanParser.Parse(null);
var default2 = TimeSpanParser.Parse("");
var default3 = TimeSpanParser.Parse("   ");

You can reference this constant in your code:

var configuredPeriod = configuration["RetentionPeriod"] 
    ?? TimeSpanParser.DefaultPeriod;
var retention = TimeSpanParser.Parse(configuredPeriod);

Conversion Factors

The parser uses the following conversion factors for approximate calculations:

Unit Days Equivalent Notes
Year 365.25 Accounts for leap years (Julian year)
Month 30.44 Average month length (365.25 / 12)
Week 7 Fixed
Day 1 Base unit

These factors are used internally by the Parse method. The GetExpressionOccurrence method uses calendar-accurate operations instead.

💡 Usage Examples

Basic Parsing

using Diginsight.Components;

public class RetentionPolicyService
{
    public void ConfigureRetention(string retentionExpression)
 {
        // Parse various duration formats using approximate conversions
        var sixMonths = TimeSpanParser.Parse("6M");
        var oneYear = TimeSpanParser.Parse("1Y");
    var twoWeeks = TimeSpanParser.Parse("2W");
        var thirtyDays = TimeSpanParser.Parse("30D");
        
        Console.WriteLine($"6M = {sixMonths.TotalDays:F1} days");  // ~182.6 days
        Console.WriteLine($"1Y = {oneYear.TotalDays:F1} days");    // ~365.25 days
        Console.WriteLine($"2W = {twoWeeks.TotalDays:F1} days"); // 14.0 days
        Console.WriteLine($"30D = {thirtyDays.TotalDays:F1} days"); // 30.0 days
    }
    
    public bool IsExpired(DateTime createdDate, string retentionPeriod)
 {
   var retention = TimeSpanParser.Parse(retentionPeriod);
        return DateTime.UtcNow - createdDate > retention;
    }
}

Calendar-Accurate Parsing with ParsingType

public class CalendarAccurateParsingService
{
    public void DemonstrateParsingModes()
    {
        var now = DateTimeOffset.UtcNow;
        
        // Generic (approximate) parsing
        var approxDuration = TimeSpanParser.Parse("6M", ParsingType.Generic, now, -1);
  Console.WriteLine($"Approximate 6M: {approxDuration.TotalDays:F1} days");
  // Always ~182.6 days regardless of reference date
        
        // Calendar-accurate parsing going backward in time
      var exactDurationBack = TimeSpanParser.Parse("6M", ParsingType.CalendarAccurate, now, -1);
        Console.WriteLine($"Exact 6M backward: {exactDurationBack.TotalDays:F1} days");
        // Actual days between now and 6 months ago
        
        // Calendar-accurate parsing going forward in time
      var exactDurationForward = TimeSpanParser.Parse("6M", ParsingType.CalendarAccurate, now, 1);
        Console.WriteLine($"Exact 6M forward: {exactDurationForward.TotalDays:F1} days");
    // Actual days between now and 6 months ahead
    }
    
    public TimeSpan GetRetentionDuration(DateTimeOffset referenceDate, string retentionPeriod)
    {
        // Get calendar-accurate duration relative to reference date
      return TimeSpanParser.Parse(
   retentionPeriod, 
ParsingType.CalendarAccurate, 
     referenceDate, 
    -1  // -1 = backward in time, 1 = forward in time
     );
    }
}

Calendar-Accurate Date Calculations

public class SubscriptionService
{
    public DateTimeOffset CalculateExpirationDate(
        DateTimeOffset startDate, 
        string retentionPeriod)
    {
        // Calculate when data should be deleted (going back in time from now)
        // This properly handles month-end dates and leap years
        var expirationDate = TimeSpanParser.GetExpressionOccurrence(
     DateTimeOffset.UtcNow, 
            retentionPeriod, 
       -1  // Negative occurrence = go backward in time
        );
        
      return expirationDate;
    }
    
    public List<DateTimeOffset> GetRetentionCheckpoints(
    DateTimeOffset currentDate, 
        string checkpointInterval, 
   int numberOfCheckpoints)
    {
        var dates = new List<DateTimeOffset>();
  var checkpointDate = currentDate;
        
        for (int i = 0; i < numberOfCheckpoints; i++)
        {
          checkpointDate = TimeSpanParser.GetExpressionOccurrence(
 checkpointDate, 
       checkpointInterval, 
           -1  // Negative occurrence = go backward in time
            );
            dates.Add(checkpointDate);
        }
  
 return dates;
    }
    
    public void DemonstrateDateAccuracy()
    {
        var today = new DateTimeOffset(2024, 3, 31, 0, 0, 0, TimeSpan.Zero);
        
     // Calculate 1 month ago from March 31
        var oneMonthAgo = TimeSpanParser.GetExpressionOccurrence(today, "1M", -1);
  // Result: February 29, 2024 (handles leap year correctly)
      
        var jan31 = new DateTimeOffset(2024, 1, 31, 0, 0, 0, TimeSpan.Zero);
        var oneMonthBeforeJan = TimeSpanParser.GetExpressionOccurrence(jan31, "1M", -1);
 // Result: December 31, 2023 (goes back 1 month accurately)
  }
}

Safe Parsing with TryParse

public class ConfigurationService
{
    private readonly ILogger<ConfigurationService> _logger;
    
    public TimeSpan GetRetentionPeriod(string userInput)
    {
        // Simple TryParse (Generic mode)
        if (TimeSpanParser.TryParse(userInput, out var duration))
        {
   _logger.LogInformation(
                "Parsed retention period: {Expression} = {Days} days", 
      userInput, 
            duration.TotalDays
   );
    return duration;
        }
 
        _logger.LogWarning(
 "Invalid retention expression: {Expression}, using default", 
            userInput
        );
  return TimeSpanParser.Parse(TimeSpanParser.DefaultPeriod);
    }
    
    public TimeSpan GetCalendarAccurateDuration(
        string userInput, 
        DateTimeOffset referenceDate, 
  int sign)
    {
        // TryParse with ParsingType.CalendarAccurate
        if (TimeSpanParser.TryParse(
            userInput, 
      ParsingType.CalendarAccurate, 
            referenceDate, 
            sign, 
            out var duration))
        {
            _logger.LogInformation(
 "Parsed calendar-accurate duration: {Expression} = {Days} days", 
       userInput, 
         duration.TotalDays
   );
            return duration;
        }
        
      _logger.LogWarning(
            "Invalid duration expression: {Expression}, using default", 
      userInput
 );
      return TimeSpanParser.Parse(
  TimeSpanParser.DefaultPeriod, 
            ParsingType.CalendarAccurate, 
            referenceDate, 
            sign
        );
    }
    
    public void ValidateUserInput(string input)
    {
        if (!TimeSpanParser.TryParse(input, out var result))
        {
            throw new ArgumentException(
                $"Invalid duration format: '{input}'. " +
          $"Use format like '6M', '1Y', or '2W3D'."
      );
        }
        
        // Further validation
        if (result.TotalDays < 1)
        {
   throw new ArgumentException("Duration must be at least 1 day");
        }
        
 if (result.TotalDays > 3650) // ~10 years
        {
         throw new ArgumentException("Duration cannot exceed 10 years");
        }
    }
}

Complex Duration Expressions

public class DurationExamples
{
    public void ComplexExpressions()
    {
        // Combine multiple units
        var complex1 = TimeSpanParser.Parse("1Y6M2W3D");
   // 1 year + 6 months + 2 weeks + 3 days
     
 var complex2 = TimeSpanParser.Parse("1Y6M2W1D12H30m45S");
 // All units combined
   
        var complex3 = TimeSpanParser.Parse("2W3D12H");
        // Skip some units (no years/months)
        
   // Fractional values
  var fractional1 = TimeSpanParser.Parse("1.5Y");   // 1.5 years
        var fractional2 = TimeSpanParser.Parse("2.5M");   // 2.5 months
var fractional3 = TimeSpanParser.Parse("1.5W3D"); // 1.5 weeks + 3 days
   
     // Comparison: approximate vs actual days
   var sixMonthsApprox = TimeSpanParser.Parse("6M");
  Console.WriteLine($"6M ≈ {sixMonthsApprox.TotalDays:F1} days");
        
  // For actual calculations going backward in time, use GetExpressionOccurrence
        var today = DateTimeOffset.UtcNow;
   var sixMonthsAgo = TimeSpanParser.GetExpressionOccurrence(today, "6M", -1);
        var actualDays = (today - sixMonthsAgo).TotalDays;
   Console.WriteLine($"6M actual = {actualDays:F1} days (calendar-accurate, going backward)");
    }
}

Precision Considerations

Be aware of the precision requirements of your application when using TimeSpanParser:

  • For approximate duration measurements, Parse(string) or ParsingType.Generic provides sufficient accuracy and simplicity.
  • When exact date calculations are needed (e.g., billing cycles, contract durations), use ParsingType.CalendarAccurate to ensure correct month and year handling.
  • For critical calculations involving legal or financial implications, prefer the clarity and precision of calendar-accurate operations.
public class PrecisionExamples
{
    public void ComparePrecision()
    {
        var referenceDate = new DateTimeOffset(2024, 1, 15, 0, 0, 0, TimeSpan.Zero);
        
        // Approximate calculation
        var approxDuration = TimeSpanParser.Parse("3M");
     var approxDate = referenceDate - approxDuration;
     Console.WriteLine($"Approximate: {approxDate:yyyy-MM-dd}");
        // Uses 30.44 * 3 = ~91.3 days
   
        // Calendar-accurate calculation
        var exactDate = TimeSpanParser.GetExpressionOccurrence(referenceDate, "3M", -1);
        Console.WriteLine($"Exact: {exactDate:yyyy-MM-dd}");
        // Uses actual calendar months
 
        // Difference can be significant
        var difference = approxDate - exactDate;
        Console.WriteLine($"Difference: {difference.TotalDays:F1} days");
    }
    
    public void LeapYearConsiderations()
    {
        var leapYearDate = new DateTimeOffset(2024, 2, 29, 0, 0, 0, TimeSpan.Zero);

        // Subtracting 1 year from Feb 29, 2024 (leap year)
    var oneYearBefore = TimeSpanParser.GetExpressionOccurrence(leapYearDate, "1Y", -1);
     // Result: Feb 28, 2023 (2023 is not a leap year, so Feb 29 doesn't exist)

        Console.WriteLine($"One year before {leapYearDate:yyyy-MM-dd} is {oneYearBefore:yyyy-MM-dd}");
    }
    
    public void EdgeCases()
    {
// Month-end boundary handling
        var jan31 = new DateTimeOffset(2024, 1, 31, 0, 0, 0, TimeSpan.Zero);
        var oneMonthBack = TimeSpanParser.GetExpressionOccurrence(jan31, "1M", -1);
     // Result: Dec 31, 2023 (both months have 31 days)
      
    var mar31 = new DateTimeOffset(2024, 3, 31, 0, 0, 0, TimeSpan.Zero);
        var oneMonthBack2 = TimeSpanParser.GetExpressionOccurrence(mar31, "1M", -1);
        // Result: Feb 29, 2024 (Feb has only 29 days in 2024, so Mar 31 -> Feb 29)
        
        // Fractional month precision
        var fractionalApprox = TimeSpanParser.Parse("2.5M");
        // 2.5 months ≈ 76.1 days (2 * 30.44 + 0.5 * 30.44)
  
      var fractionalExact = TimeSpanParser.Parse("2.5M", ParsingType.CalendarAccurate, jan31, -1);
        // Actual duration from Jan 31 going back 2 whole months + 15 days (0.5 * 30.44)
    }
}

📚 Reference

Enums

ParsingType

public enum ParsingType
{
    /// <summary>
    /// Uses approximate conversions (1 year = 365.25 days, 1 month = 30.44 days).
    /// </summary>
    Generic,
    
    /// <summary>
    /// Uses calendar-accurate calculations with actual month/year handling relative to a reference date.
  /// </summary>
    CalendarAccurate
}

Defines the parsing strategy for duration expressions.

Classes

  • TimeSpanParser: Static class providing duration parsing functionality
  • TimeSpanParser.Examples: Static class containing predefined example constants

Methods

Parse (Simple)

public static TimeSpan Parse(string expression)

Parses an extended TimeSpan expression into a TimeSpan using approximate conversions.

Parameters: - expression (string): Duration expression (e.g., “6M”, “1.5Y”, “2W3D”) or standard TimeSpan format. If null/whitespace, returns DefaultPeriod.

Returns: TimeSpan representing the approximate duration.

Exceptions: - ArgumentException: Invalid expression format.

Example:

var duration = TimeSpanParser.Parse("6M");
var standard = TimeSpanParser.Parse("01:30:00");

Parse (With ParsingType)

public static TimeSpan Parse(
    string expression, 
    ParsingType parsingType, 
    DateTimeOffset referenceDateTimeOffset, 
    int sign)

Parses an extended TimeSpan expression into a TimeSpan using the specified parsing strategy.

Parameters: - expression (string): Duration expression to parse. If null/whitespace, returns DefaultPeriod. - parsingType (ParsingType): The parsing strategy (Generic or CalendarAccurate). - referenceDateTimeOffset (DateTimeOffset): Reference date for CalendarAccurate evaluation (ignored for Generic). - sign (int): Direction for CalendarAccurate evaluation: -1 for backward in time, 1 for forward in time (ignored for Generic).

Returns: - For ParsingType.Generic: Approximate duration using average values. - For ParsingType.CalendarAccurate: Actual duration between the reference date and the date after applying the expression in the specified direction.

Exceptions: - ArgumentException: Invalid expression format.

Example:

var now = DateTimeOffset.Now;

// Generic parsing (approximate)
var approxSixMonths = TimeSpanParser.Parse("6M", ParsingType.Generic, now, -1);

// Calendar-accurate parsing
var exactSixMonthsBack = TimeSpanParser.Parse("6M", ParsingType.CalendarAccurate, now, -1);
var exactOneYearForward = TimeSpanParser.Parse("1Y", ParsingType.CalendarAccurate, now, 1);

TryParse (Simple)

public static bool TryParse(string expression, out TimeSpan result)

Attempts to parse an extended TimeSpan expression without throwing exceptions.

Parameters: - expression (string): Duration expression to parse. - result (out TimeSpan): Parsed TimeSpan if successful, otherwise TimeSpan.Zero.

Returns: true if parsing succeeded, otherwise false.

Example:

if (TimeSpanParser.TryParse("6M", out var duration))
{
    Console.WriteLine($"Parsed: {duration.TotalDays} days");
}

TryParse (With ParsingType)

public static bool TryParse(
    string expression, 
    ParsingType parsingType, 
    DateTimeOffset referenceDateTimeOffset, 
    int sign, 
    out TimeSpan result)

Attempts to parse an extended TimeSpan expression without throwing exceptions, using the specified parsing strategy.

Parameters: - expression (string): Duration expression to parse. - parsingType (ParsingType): The parsing strategy (Generic or CalendarAccurate). - referenceDateTimeOffset (DateTimeOffset): Reference date for CalendarAccurate evaluation (ignored for Generic). - sign (int): Direction for CalendarAccurate evaluation: -1 for backward, 1 for forward (ignored for Generic). - result (out TimeSpan): Parsed TimeSpan if successful, otherwise TimeSpan.Zero.

Returns: true if parsing succeeded, otherwise false.

Example:

var now = DateTimeOffset.Now;

if (TimeSpanParser.TryParse("6M", ParsingType.CalendarAccurate, now, -1, out var duration))
{
    Console.WriteLine($"Duration: {duration.TotalDays} days");
}
else
{
    Console.WriteLine("Invalid expression");
}

GetExpressionOccurrence

public static DateTimeOffset GetExpressionOccurrence(
    DateTimeOffset referenceDate, 
    string expression, 
  int occurrence)

Calculates a date/time by applying an extended duration expression to a reference date using calendar-accurate operations.

Parameters: - referenceDate (DateTimeOffset): Starting date/time. - expression (string): Duration expression. If null/whitespace, uses DefaultPeriod. - occurrence (int): Number of times to apply the duration. Negative values move backward in time, positive values move forward.

Returns: DateTimeOffset representing the calculated date.

Exceptions: - ArgumentException: Invalid expression format.

Example:

var today = DateTimeOffset.Now;
var sixMonthsAgo = TimeSpanParser.GetExpressionOccurrence(today, "6M", -1);
var oneYearAgo = TimeSpanParser.GetExpressionOccurrence(today, "1Y", -1);
var twoYearsAgo = TimeSpanParser.GetExpressionOccurrence(today, "1Y", -2);
var sixMonthsAhead = TimeSpanParser.GetExpressionOccurrence(today, "6M", 1);
var oneYearAhead = TimeSpanParser.GetExpressionOccurrence(today, "1Y", 1);

Constants

DefaultPeriod

public const string DefaultPeriod = "1M";

The default period used when parsing expressions. If the expression is null or whitespace, this period is used.

Example:

var duration = TimeSpanParser.Parse(null);  // Uses DefaultPeriod, equivalent to "1M"

Expression Format

Extended duration expressions are specified using a combination of the following units:

  • Y or y: Years (e.g., “1Y”, “2.5Y”) - case-insensitive
  • M: Months (e.g., “6M”, “1.5M”) - uppercase only
  • W or w: Weeks (e.g., “2W”, “1.5W”) - case-insensitive
  • D or d: Days (e.g., “30D”, “1.5D”) - case-insensitive
  • H or h: Hours (e.g., “12H”, “0.5H”) - case-insensitive
  • m: Minutes (e.g., “30m”, “1.5m”) - lowercase only
  • S or s: Seconds (e.g., “45S”, “1.5S”) - case-insensitive

Format Rules: - Units must appear in the order shown above (descending: Years → Months → Weeks → Days → Hours → Minutes → Seconds) - Any unit can be omitted - Fractional values are supported for all units (e.g., “1.5Y”, “2.5M”) - No spaces allowed between components - M (uppercase) and m (lowercase) are case-sensitive to distinguish months from minutes

Regular Expression Pattern:

^(?:(\d+(?:\.\d+)?)[Yy])?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)[Ww])?(?:(\d+(?:\.\d+)?)[Dd])?(?:(\d+(?:\.\d+)?)[Hh])?(?:(\d+(?:\.\d+)?)m)?(?:(\d+(?:\.\d+)?)[Ss])?$

Valid Examples: - "6M" - 6 months - "1.5Y" - 1.5 years - "2W3D" - 2 weeks and 3 days - "1Y6M2W1D12H30m45S" - All units combined - "30m" - 30 minutes - "2W3d" - 2 weeks and 3 days (mixed case for case-insensitive units)

Invalid Examples: - "6m" without preceding digits would be interpreted as 6 minutes, not 6 months - "30M" - Cannot be interpreted as 30 minutes (M is months, not minutes) - "3D2W" - Wrong order (weeks must come before days) - "+1Y" or "1Y1M1D" (ambiguous with plus sign or missing unit)

Back to top